/*
 * cache.c
 *
 *  Created on: Nov 19, 2013
 *      Author: adijoshi
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "cache.h"

void init_Cache()
{
	INDEX_BITS = log2(SETS);
	BYTE_OFFSET = log2(BYTES_PER_LINE);
	TAG_BITS = 32-INDEX_BITS-BYTE_OFFSET;

	int set=0;
	int way=0;
	for(set=0; set<SETS; set++)
		for(way=0; way<WAYS; way++)
		{
			set_Line_Status(set,way,I);
			set_Line_LRU(set,way,-1);
			set_Line_Tag(set,way,0);
		}
}

void exec_Read_Req(unsigned int operation, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopResult;
	//LineStatus oldStatus;

	set = extract_Index(address);
	way = match_Tag(set,address);

	if(way == -1)
	{
		print_Output("R",address);
		snoopResult = get_Snoop_Result('R',address);
		// if snoop result = 0, call read from memory
		way = fill_Cache_Line(set,address);
		update_LRU(set,way);
		//oldStatus = update_MESI(operation,snoopResult,set,way);
		update_MESI(operation,snoopResult,set,way);
		// increment cache miss count
		// increment cache write count
		// increment cache read count
	}
	else // L2 read hit
	{
		update_LRU(set,way);
		// increment cache hit count
		// increment cache read count
		// print_output to file
	}
}

void exec_Write_Req(unsigned int operation, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopResult=0;
	LineStatus oldStatus;

	set = extract_Index(address);
	way = match_Tag(set,address);

	if(way == -1)
	{
		print_Output("M",address);
		snoopResult = get_Snoop_Result('M',address);
		// if snoop result = 0, call read from memory
		way = fill_Cache_Line(set,address);
		update_LRU(set,way);
		oldStatus = update_MESI(operation,snoopResult,set,way);
		// increment cache miss count
		// increment cache write count
		// increment cache read count
	}
	else // L2 write hit
	{
		oldStatus = update_MESI(operation,snoopResult,set,way);
		if(oldStatus == S)
			print_Output("I",address);
		update_LRU(set,way);
		// increment cache hit count
		// increment cache read count
		// print_output to file ??
	}
}

void print_Output(char* operation, unsigned int address)
{
		if(OUTPUT_ON)		//print to console
		{
			printf("%s",operation);
			if(address == -1)
				printf("\n");
			else
				printf(" %x\n",address);
		}
		else				// write to file
		{
			FILE * output_file_ptr;
			output_file_ptr = fopen(FILENAME,"a");

			fprintf(output_file_ptr,"%s",operation);
			if(address == -1)
				fprintf(output_file_ptr,"\n");
			else
				fprintf(output_file_ptr," %x\n",address);

			fflush(output_file_ptr);
			fclose(output_file_ptr);
		}
}

unsigned int extract_Index(unsigned int address)
{
	return (address << TAG_BITS) >> (32-INDEX_BITS);
}

unsigned int extract_Tag(unsigned int address)
{
	return (address >> (32-TAG_BITS));
}

unsigned int extract_Byte_Offset(unsigned int address)
{
	return (address << (TAG_BITS+INDEX_BITS)) >> (32-BYTE_OFFSET);
}

int match_Tag(int set, unsigned int address)
{
	int way = -1;
	int i=0;
	for(i=0;i<WAYS;i++)
		if(get_Line_Tag(set,i) == extract_Tag(address) && (get_Line_Status(set,i) != I))
		{
			way = i;
			break;
		}
	return way;
}

LineStatus get_Line_Status(int set,int way)
{
	return L2Cache[set][way].MESI_Status;
}

void set_Line_Status(int set,int way,LineStatus nextStatus)
{
	L2Cache[set][way].MESI_Status = nextStatus;
}

int get_Line_Tag(int set,int way)
{
	return L2Cache[set][way].Tag;
}

void set_Line_Tag(int set,int way,int tag)
{
	L2Cache[set][way].Tag = tag;
}

int get_Line_LRU(int set,int way)
{
	return L2Cache[set][way].LRU_Status;
}

void set_Line_LRU(int set,int way,int lruStatus)
{
	L2Cache[set][way].LRU_Status = lruStatus;
}

int get_Snoop_Result(char operation, unsigned int address)
{
	/*int snoopResult=0;
	int snoopBits;

	snoopBits = (address & 3);

	if (snoopBits == 0)
		snoopResult = 0;
	else if (snoopBits == 1 || snoopBits == 2)
		snoopResult = 1;
	else if (snoopBits == 3)
		snoopResult = 2;

	return snoopResult;*/

	if ((address & 3) == 1 || (address & 3) == 2)
		return 1;
	else if ((address & 3) == 3)
		return 2;
	else
		return 0;
}

void put_Snoop_Result(char operation, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopedOperation;
	LineStatus status;

	switch(operation){
	case 'R':
		snoopedOperation = 4;
		break;
	case 'W':
		snoopedOperation = 5;
		break;
	case 'M':
		snoopedOperation = 6;
		break;
	case 'I':
		snoopedOperation = 3;
		break;
	}

	set = extract_Index(address);
	way = match_Tag(set,address);

	if(way == -1)
	{
		print_Output("SR no HIT",-1);
	}
	else
	{
		status = get_Line_Status(set,way);
		switch(operation)
		{
			case 'R':
				if (status == M)
					print_Output("SR HITM",-1);
					//call WriteBack function;
				else if (status == E)
					print_Output("SR HIT",-1);
				update_MESI(snoopedOperation,0,set,way); // update MESI to S
				break;

			case 'W':
				// do nothing
				print_Output("SR no HIT",-1);
				break;

			case 'M':
				if (status == M)
					print_Output("SR HITM",-1);
					//call WriteBack function;
				update_MESI(snoopedOperation,0,set,way); // update MESI to I
				// tell L1 about invalidation
				break;

			case 'I':
				update_MESI(snoopedOperation,0,set,way); // update MESI to I
				// tell L1 about invalidation
				break;

			default :
				break;
		}

	}
}

void clear_Cache()
{
	int set=0,way=0;

	for(set=0; set<SETS; set++)
		for(way=0; way<WAYS; way++)
		{
			set_Line_Status(set,way,I);
			set_Line_LRU(set,way,-1);
			set_Line_Tag(set,way,0);
		}
}

void print_Cache()
{
	int set=0,way=0;

	for(set = 0; set < SETS; set++)
		for (way = 0; way < WAYS; way++)
			if (get_Line_Status(set,way) != I)
			{
				printf(" CACHE LINE[%d] STATUS	= %d\n", set, way);
				printf(" CACHE LINE[%d] CONTENT	= %x\n", way, L2Cache[set][way]);
			}
}

int fill_Cache_Line(int set,unsigned int address)
{
	int way=-1;
	int i=0;
	for(i=0;i<WAYS;i++)
	{
		if(get_Line_Status(set,i) != I)
			way = i;
	}

	if(way == -1)
		way = find_LRU_way(set);
		// eviction communication to L1 -- base address calculation
		// if evicted way is M then do a write back

	set_Line_Tag(set,way,extract_Tag(address));
	return way;
}

LineStatus update_MESI(unsigned int operation, int snoopResult, int set, int way)
{
	LineStatus currentStatus = get_Line_Status(set, way);
	LineStatus nextStatus;

	switch (currentStatus) {
	case I:
		if ((operation == CPU_READ_D || operation == CPU_READ_I ) && snoopResult == SNOOPED_HIT)
			nextStatus = S;
		else if ((operation == CPU_READ_D || operation == CPU_READ_I) && snoopResult == SNOOPED_MISS)
			nextStatus = E;
		else if (operation == CPU_WRITE_D)
			nextStatus = M;
		else
			nextStatus = I;
		break;

	case M:
		if (operation == CPU_READ_D || operation == CPU_READ_I)
			nextStatus = M;
		else if (operation == CPU_WRITE_D)
			nextStatus = M;
		else if (operation == SNOOPED_RFO)
			nextStatus = I;
		else if (operation == SNOOPED_READ)
			nextStatus = S;
		break;

	case S:
		if (operation == CPU_READ_D || operation == CPU_READ_I)
			nextStatus = S;
		else if (operation == CPU_WRITE_D)
			nextStatus = M;
		else if (operation == SNOOPED_READ)
			nextStatus = S;
		else if (operation == SNOOPED_RFO)
			nextStatus = I;
		break;

	case E:
		if (operation == CPU_READ_D || operation == CPU_READ_I)
			nextStatus = E;
		else if (operation == CPU_READ_D)
			nextStatus = M;
		else if (operation == SNOOPED_RFO)
			nextStatus = I;
		else if (operation == SNOOPED_READ)
			nextStatus = S;
		break;
	}

	set_Line_Status(set,way,nextStatus);
	return currentStatus;
}

void update_LRU(int set, int way)
{
	set_Line_LRU(set,way,0);

	int i=0;
	for (i = 0; i<WAYS; i++)
	{
		if ((get_Line_LRU(set,i)!= -1) && (get_Line_LRU(set,i) <= get_Line_LRU(set,way)) && (i!=way))
		{
			int oldLRUStatus = get_Line_LRU(set,i);
			set_Line_LRU(set,way,oldLRUStatus++);
		}
	}
}

int find_LRU_way(int set)
{
	int way;
	for (way = 0; way < WAYS; way++)
	{
		if (get_Line_LRU(set,way) == WAYS-1)
		{
			break;
		}
	}
	return way;
}
